@hoardodile/create-plugin 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22,12 +22,14 @@ var SDK_DEP_NAMES = [
22
22
 
23
23
  // src/rewrite.ts
24
24
  var THIRD_PARTY_VERSIONS = {
25
+ "@release-it/conventional-changelog": "^12.0.0",
25
26
  "@types/node": "^26.2.0",
26
27
  "@types/react": "^19.2.18",
27
28
  "@types/react-dom": "^19.2.4",
28
29
  jsdom: "^30.0.1",
29
30
  react: "^19.2.8",
30
31
  "react-dom": "^19.2.8",
32
+ "release-it": "^21.0.2",
31
33
  typescript: "^7.0.2",
32
34
  vite: "^8.2.2",
33
35
  vitest: "^4.1.11"
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sdk-deps.gen.ts","../src/rewrite.ts","../src/index.ts"],"names":["join"],"mappings":"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","file":"index.js","sourcesContent":["// GENERATED by scripts/gen-sdk-deps.mjs — never hand-edit.\n// The published SDK closure a scaffolded plugin can depend on; kept in\n// lockstep with scripts/lib/sdk-closure.mjs (see sdks:pack).\nexport const SDK_DEP_NAMES: readonly string[] = [\n\t\"@hoardodile/cli\",\n\t\"@hoardodile/host\",\n\t\"@hoardodile/host-web\",\n\t\"@hoardodile/i18n\",\n\t\"@hoardodile/sdk-react\",\n\t\"@hoardodile/sdk-server\",\n\t\"@hoardodile/sdk-types\",\n\t\"@hoardodile/sdk-web\",\n\t\"@hoardodile/ui\",\n\t\"@hoardodile/workbench\",\n]\n","/**\n * Scaffold-time rewrites applied to the embedded template copy: fresh\n * manifest identity and concrete dependency specs (the template uses\n * `workspace:*` / `catalog:` which only resolve inside the monorepo).\n */\nimport { join } from \"node:path\"\nimport { SDK_DEP_NAMES } from \"./sdk-deps.gen.ts\"\n\n/** Concrete versions for the catalog: specs the template uses. */\nexport const THIRD_PARTY_VERSIONS: Readonly<Record<string, string>> = {\n\t\"@types/node\": \"^26.2.0\",\n\t\"@types/react\": \"^19.2.18\",\n\t\"@types/react-dom\": \"^19.2.4\",\n\tjsdom: \"^30.0.1\",\n\treact: \"^19.2.8\",\n\t\"react-dom\": \"^19.2.8\",\n\ttypescript: \"^7.0.2\",\n\tvite: \"^8.2.2\",\n\tvitest: \"^4.1.11\",\n}\n\nexport type ManifestShape = {\n\tid: string\n\tname: string\n\tdescription: string\n\tversion: string\n\ti18n?: {\n\t\tname?: Record<string, string>\n\t\tdescription?: Record<string, string>\n\t}\n}\n\nexport function rewriteManifest(\n\tmanifest: ManifestShape,\n\tpluginName: string,\n): ManifestShape {\n\tmanifest.id = crypto.randomUUID()\n\tmanifest.name = pluginName\n\tmanifest.description = `Content plugin: ${pluginName}`\n\tif (manifest.i18n?.name !== undefined) {\n\t\tmanifest.i18n.name.en = pluginName\n\t}\n\tif (manifest.i18n?.description !== undefined) {\n\t\tmanifest.i18n.description.en = `Content plugin: ${pluginName}`\n\t}\n\tmanifest.version = \"0.0.1\"\n\treturn manifest\n}\n\nexport type PackageJson = {\n\tname: string\n\tversion?: string\n\tdependencies?: Record<string, string>\n\tdevDependencies?: Record<string, string>\n}\n\nexport function tarballSpec(\n\ttarballsDir: string,\n\tdep: string,\n\tversion: string,\n): string {\n\tconst fileName = dep.replace(\"@\", \"\").replace(\"/\", \"-\")\n\treturn `file:${join(tarballsDir, `${fileName}-${version}.tgz`).replaceAll(\"\\\\\", \"/\")}`\n}\n\nfunction rewriteDepBlock(\n\tdeps: Record<string, string> | undefined,\n\ttarballsDir: string | undefined,\n\tsdkVersion: string,\n): Record<string, string> {\n\tconst out = { ...deps }\n\tfor (const [dep, spec] of Object.entries(out)) {\n\t\tif (SDK_DEP_NAMES.includes(dep)) {\n\t\t\tout[dep] =\n\t\t\t\ttarballsDir !== undefined\n\t\t\t\t\t? tarballSpec(tarballsDir, dep, sdkVersion)\n\t\t\t\t\t: // `^0.2.0-alpha.3` would never match the 0.2.0 stable\n\t\t\t\t\t\t// release (semver keeps prereleases out of plain ranges),\n\t\t\t\t\t\t// so npm specs drop the prerelease suffix.\n\t\t\t\t\t\t`^${sdkVersion.split(\"-\")[0]}`\n\t\t} else if (spec === \"catalog:\") {\n\t\t\tconst version = THIRD_PARTY_VERSIONS[dep]\n\t\t\tif (version === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`template dependency \"${dep}\" has no concrete version mapping`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tout[dep] = version\n\t\t}\n\t}\n\treturn out\n}\n\nexport function rewritePackageJson(\n\tpkg: PackageJson,\n\tpluginName: string,\n\topts: { readonly tarballsDir?: string; readonly sdkVersion: string },\n): PackageJson {\n\tpkg.name = pluginName\n\tdelete pkg.version\n\tpkg.version = \"0.0.1\"\n\tif (pkg.dependencies !== undefined) {\n\t\tpkg.dependencies = rewriteDepBlock(\n\t\t\tpkg.dependencies,\n\t\t\topts.tarballsDir,\n\t\t\topts.sdkVersion,\n\t\t)\n\t}\n\tif (pkg.devDependencies !== undefined) {\n\t\tpkg.devDependencies = rewriteDepBlock(\n\t\t\tpkg.devDependencies,\n\t\t\topts.tarballsDir,\n\t\t\topts.sdkVersion,\n\t\t)\n\t}\n\treturn pkg\n}\n\n/**\n * Install-script dependencies that land in a scaffolded plugin through\n * host's optionalDependencies (the same names as the root workspace's\n * allowBuilds). pnpm refuses to run their build scripts without an\n * explicit approval, so the generated workspace approves them up front.\n */\nconst ALLOWED_BUILDS = [\n\t\"@derhuerst/ffprobe-static\",\n\t\"@hoardodile/7z-bin\",\n\t\"ffmpeg-static\",\n]\n\nexport function tarballOverridesYaml(\n\ttarballsDir: string,\n\tsdkVersion: string,\n): string {\n\tconst lines = SDK_DEP_NAMES.map(\n\t\t(dep) => ` '${dep}': '${tarballSpec(tarballsDir, dep, sdkVersion)}'`,\n\t).join(\"\\n\")\n\tconst builds = ALLOWED_BUILDS.map((dep) => ` '${dep}': true`).join(\"\\n\")\n\treturn [\n\t\t\"# Redirect the cross-SDK 0.0.0 specs packed into the tarballs to the\",\n\t\t\"# sibling tarballs in this workspace; approve the install scripts of\",\n\t\t\"# host's optional binaries (ffmpeg/ffprobe/7-Zip) so pnpm runs them.\",\n\t\t\"overrides:\",\n\t\tlines,\n\t\t\"allowBuilds:\",\n\t\tbuilds,\n\t\t\"\",\n\t].join(\"\\n\")\n}\n","#!/usr/bin/env node\n/**\n * `create-hoardodile-plugin` — scaffold a new content plugin from the\n * embedded template copy (`src/template/`, kept in sync with\n * `plugins/template` by scripts/sync-template.mjs).\n *\n * Flow: interactive prompts (name, id auto-generated) → copy template →\n * rewrite manifest + package.json → install dependencies → validate the\n * manifest → print the dev loop entry points.\n *\n * create-hoardodile-plugin my-plugin\n * create-hoardodile-plugin my-plugin --tarballs ../hoardodile/tmp/sdks\n *\n * `--tarballs <dir>` rewires the SDK deps to packed tarballs plus a\n * pnpm-workspace.yaml with the cross-package overrides — used by the\n * release smoke test and for offline installs.\n */\nimport { spawn } from \"node:child_process\"\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport {\n\tcancel,\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\toutro,\n\ttext,\n} from \"@clack/prompts\"\nimport { pluginManifest } from \"@hoardodile/sdk-types/schema\"\nimport {\n\trewriteManifest,\n\trewritePackageJson,\n\ttarballOverridesYaml,\n} from \"./rewrite.ts\"\n\nconst ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\")\n// The template ships inside dist (dist/template) — the bin always runs\n// from the built artifact, so resolve relative to this module.\nconst TEMPLATE_DIR = resolve(\n\tdirname(fileURLToPath(import.meta.url)),\n\t\"template\",\n)\nconst SELF_VERSION = JSON.parse(\n\treadFileSync(join(ROOT, \"package.json\"), \"utf8\"),\n).version\n\nfunction writeJson(path: string, value: unknown): void {\n\twriteFileSync(path, `${JSON.stringify(value, null, \"\\t\")}\\n`)\n}\n\nfunction writeTarballOverrides(targetDir: string, tarballsDir: string): void {\n\twriteFileSync(\n\t\tjoin(targetDir, \"pnpm-workspace.yaml\"),\n\t\ttarballOverridesYaml(tarballsDir, SELF_VERSION),\n\t)\n}\n\nasync function runInstall(targetDir: string): Promise<void> {\n\tlog.step(\"Installing dependencies (pnpm install)\")\n\treturn new Promise<void>((resolveDone, reject) => {\n\t\tconst child = spawn(\"pnpm\", [\"install\"], {\n\t\t\tcwd: targetDir,\n\t\t\tstdio: \"inherit\",\n\t\t\tshell: process.platform === \"win32\",\n\t\t})\n\t\tchild.on(\"error\", reject)\n\t\tchild.on(\"exit\", (code) =>\n\t\t\tcode === 0\n\t\t\t\t? resolveDone()\n\t\t\t\t: reject(new Error(`pnpm install exited ${code}`)),\n\t\t)\n\t})\n}\n\nexport async function main() {\n\tconst positional = process.argv.slice(2).filter((a) => !a.startsWith(\"--\"))\n\tconst tarballsFlag = process.argv.indexOf(\"--tarballs\")\n\tconst tarballsDir =\n\t\ttarballsFlag !== -1\n\t\t\t? resolve(process.argv[tarballsFlag + 1] ?? \"\")\n\t\t\t: undefined\n\n\tintro(\"hoardodile plugin scaffold\")\n\n\tconst providedName = positional[0]\n\tconst pluginName =\n\t\tprovidedName ??\n\t\t(await text({\n\t\t\tmessage: \"Plugin name (also the directory name, npm-style):\",\n\t\t\tvalidate: (v) =>\n\t\t\t\t/^[a-z0-9][a-z0-9-]*$/.test(v ?? \"\")\n\t\t\t\t\t? undefined\n\t\t\t\t\t: \"lowercase letters, digits and dashes only\",\n\t\t}))\n\tif (isCancel(pluginName)) {\n\t\tcancel(\"scaffold cancelled\")\n\t\tprocess.exit(1)\n\t}\n\n\tconst targetDir = resolve(pluginName)\n\tif (existsSync(targetDir) && readdirSync(targetDir).length > 0) {\n\t\tconst proceed = await confirm({\n\t\t\tmessage: `${targetDir} is not empty — scaffold into it anyway?`,\n\t\t\tinitialValue: false,\n\t\t})\n\t\tif (isCancel(proceed) || !proceed) {\n\t\t\tcancel(\"scaffold cancelled\")\n\t\t\tprocess.exit(1)\n\t\t}\n\t}\n\n\tlog.step(`Copying template into ${targetDir}`)\n\tmkdirSync(targetDir, { recursive: true })\n\t// dist/template is a build artifact (already filtered when copied in);\n\t// copy it wholesale so template source dirs inside it survive.\n\tcpSync(TEMPLATE_DIR, targetDir, { recursive: true })\n\n\tlog.step(\"Rewriting manifest and package.json\")\n\tconst manifestPath = join(targetDir, \"manifest.json\")\n\tconst pkgPath = join(targetDir, \"package.json\")\n\twriteJson(\n\t\tmanifestPath,\n\t\trewriteManifest(JSON.parse(readFileSync(manifestPath, \"utf8\")), pluginName),\n\t)\n\twriteJson(\n\t\tpkgPath,\n\t\trewritePackageJson(JSON.parse(readFileSync(pkgPath, \"utf8\")), pluginName, {\n\t\t\ttarballsDir,\n\t\t\tsdkVersion: SELF_VERSION,\n\t\t}),\n\t)\n\n\tconst parsed = pluginManifest.safeParse(\n\t\tJSON.parse(readFileSync(manifestPath, \"utf8\")),\n\t)\n\tif (!parsed.success) {\n\t\tprocess.exitCode = 1\n\t\tlog.error(`generated manifest is invalid: ${parsed.error.message}`)\n\t\treturn\n\t}\n\n\tif (tarballsDir !== undefined) {\n\t\tlog.step(`Rewiring SDK deps to tarballs in ${tarballsDir}`)\n\t\twriteTarballOverrides(targetDir, tarballsDir)\n\t}\n\n\ttry {\n\t\tawait runInstall(targetDir)\n\t} catch (err) {\n\t\tprocess.exitCode = 1\n\t\tlog.error(\n\t\t\t`install failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t)\n\t\treturn\n\t}\n\n\toutro(\"Plugin scaffolded. Next steps:\")\n\tconsole.log(` cd ${targetDir}`)\n\tconsole.log(\n\t\t\" pnpm build # build dist/ (client + server bundle + manifest)\",\n\t)\n\tconsole.log(\n\t\t\" pnpm dev # watch-build + serve the workbench (http://127.0.0.1:5199)\",\n\t)\n\tconsole.log(\" pnpm test # unit tests against the fixture API\")\n\tconsole.log(\" hoardodile plugin run detect testdata --plugin-dir dist\")\n\tconsole.log(\n\t\t\" # Replace the manifest id? No — it was generated fresh. Zip dist/ contents and upload in Settings → Plugins.\",\n\t)\n}\n\nif (\n\tprocess.argv[1] !== undefined &&\n\tresolve(process.argv[1]) === fileURLToPath(import.meta.url)\n) {\n\tmain().catch((err) => {\n\t\tlog.error(err instanceof Error ? err.message : String(err))\n\t\tprocess.exit(1)\n\t})\n}\n"]}
1
+ {"version":3,"sources":["../src/sdk-deps.gen.ts","../src/rewrite.ts","../src/index.ts"],"names":["join"],"mappings":"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","file":"index.js","sourcesContent":["// GENERATED by scripts/gen-sdk-deps.mjs — never hand-edit.\n// The published SDK closure a scaffolded plugin can depend on; kept in\n// lockstep with scripts/lib/sdk-closure.mjs (see sdks:pack).\nexport const SDK_DEP_NAMES: readonly string[] = [\n\t\"@hoardodile/cli\",\n\t\"@hoardodile/host\",\n\t\"@hoardodile/host-web\",\n\t\"@hoardodile/i18n\",\n\t\"@hoardodile/sdk-react\",\n\t\"@hoardodile/sdk-server\",\n\t\"@hoardodile/sdk-types\",\n\t\"@hoardodile/sdk-web\",\n\t\"@hoardodile/ui\",\n\t\"@hoardodile/workbench\",\n]\n","/**\n * Scaffold-time rewrites applied to the embedded template copy: fresh\n * manifest identity and concrete dependency specs (the template uses\n * `workspace:*` / `catalog:` which only resolve inside the monorepo).\n */\nimport { join } from \"node:path\"\nimport { SDK_DEP_NAMES } from \"./sdk-deps.gen.ts\"\n\n/** Concrete versions for the catalog: specs the template uses. */\nexport const THIRD_PARTY_VERSIONS: Readonly<Record<string, string>> = {\n\t\"@release-it/conventional-changelog\": \"^12.0.0\",\n\t\"@types/node\": \"^26.2.0\",\n\t\"@types/react\": \"^19.2.18\",\n\t\"@types/react-dom\": \"^19.2.4\",\n\tjsdom: \"^30.0.1\",\n\treact: \"^19.2.8\",\n\t\"react-dom\": \"^19.2.8\",\n\t\"release-it\": \"^21.0.2\",\n\ttypescript: \"^7.0.2\",\n\tvite: \"^8.2.2\",\n\tvitest: \"^4.1.11\",\n}\n\nexport type ManifestShape = {\n\tid: string\n\tname: string\n\tdescription: string\n\tversion: string\n\ti18n?: {\n\t\tname?: Record<string, string>\n\t\tdescription?: Record<string, string>\n\t}\n}\n\nexport function rewriteManifest(\n\tmanifest: ManifestShape,\n\tpluginName: string,\n): ManifestShape {\n\tmanifest.id = crypto.randomUUID()\n\tmanifest.name = pluginName\n\tmanifest.description = `Content plugin: ${pluginName}`\n\tif (manifest.i18n?.name !== undefined) {\n\t\tmanifest.i18n.name.en = pluginName\n\t}\n\tif (manifest.i18n?.description !== undefined) {\n\t\tmanifest.i18n.description.en = `Content plugin: ${pluginName}`\n\t}\n\tmanifest.version = \"0.0.1\"\n\treturn manifest\n}\n\nexport type PackageJson = {\n\tname: string\n\tversion?: string\n\tdependencies?: Record<string, string>\n\tdevDependencies?: Record<string, string>\n}\n\nexport function tarballSpec(\n\ttarballsDir: string,\n\tdep: string,\n\tversion: string,\n): string {\n\tconst fileName = dep.replace(\"@\", \"\").replace(\"/\", \"-\")\n\treturn `file:${join(tarballsDir, `${fileName}-${version}.tgz`).replaceAll(\"\\\\\", \"/\")}`\n}\n\nfunction rewriteDepBlock(\n\tdeps: Record<string, string> | undefined,\n\ttarballsDir: string | undefined,\n\tsdkVersion: string,\n): Record<string, string> {\n\tconst out = { ...deps }\n\tfor (const [dep, spec] of Object.entries(out)) {\n\t\tif (SDK_DEP_NAMES.includes(dep)) {\n\t\t\tout[dep] =\n\t\t\t\ttarballsDir !== undefined\n\t\t\t\t\t? tarballSpec(tarballsDir, dep, sdkVersion)\n\t\t\t\t\t: // `^0.2.0-alpha.3` would never match the 0.2.0 stable\n\t\t\t\t\t\t// release (semver keeps prereleases out of plain ranges),\n\t\t\t\t\t\t// so npm specs drop the prerelease suffix.\n\t\t\t\t\t\t`^${sdkVersion.split(\"-\")[0]}`\n\t\t} else if (spec === \"catalog:\") {\n\t\t\tconst version = THIRD_PARTY_VERSIONS[dep]\n\t\t\tif (version === undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`template dependency \"${dep}\" has no concrete version mapping`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tout[dep] = version\n\t\t}\n\t}\n\treturn out\n}\n\nexport function rewritePackageJson(\n\tpkg: PackageJson,\n\tpluginName: string,\n\topts: { readonly tarballsDir?: string; readonly sdkVersion: string },\n): PackageJson {\n\tpkg.name = pluginName\n\tdelete pkg.version\n\tpkg.version = \"0.0.1\"\n\tif (pkg.dependencies !== undefined) {\n\t\tpkg.dependencies = rewriteDepBlock(\n\t\t\tpkg.dependencies,\n\t\t\topts.tarballsDir,\n\t\t\topts.sdkVersion,\n\t\t)\n\t}\n\tif (pkg.devDependencies !== undefined) {\n\t\tpkg.devDependencies = rewriteDepBlock(\n\t\t\tpkg.devDependencies,\n\t\t\topts.tarballsDir,\n\t\t\topts.sdkVersion,\n\t\t)\n\t}\n\treturn pkg\n}\n\n/**\n * Install-script dependencies that land in a scaffolded plugin through\n * host's optionalDependencies (the same names as the root workspace's\n * allowBuilds). pnpm refuses to run their build scripts without an\n * explicit approval, so the generated workspace approves them up front.\n */\nconst ALLOWED_BUILDS = [\n\t\"@derhuerst/ffprobe-static\",\n\t\"@hoardodile/7z-bin\",\n\t\"ffmpeg-static\",\n]\n\nexport function tarballOverridesYaml(\n\ttarballsDir: string,\n\tsdkVersion: string,\n): string {\n\tconst lines = SDK_DEP_NAMES.map(\n\t\t(dep) => ` '${dep}': '${tarballSpec(tarballsDir, dep, sdkVersion)}'`,\n\t).join(\"\\n\")\n\tconst builds = ALLOWED_BUILDS.map((dep) => ` '${dep}': true`).join(\"\\n\")\n\treturn [\n\t\t\"# Redirect the cross-SDK 0.0.0 specs packed into the tarballs to the\",\n\t\t\"# sibling tarballs in this workspace; approve the install scripts of\",\n\t\t\"# host's optional binaries (ffmpeg/ffprobe/7-Zip) so pnpm runs them.\",\n\t\t\"overrides:\",\n\t\tlines,\n\t\t\"allowBuilds:\",\n\t\tbuilds,\n\t\t\"\",\n\t].join(\"\\n\")\n}\n","#!/usr/bin/env node\n/**\n * `create-hoardodile-plugin` — scaffold a new content plugin from the\n * embedded template copy (`src/template/`, kept in sync with\n * `plugins/template` by scripts/sync-template.mjs).\n *\n * Flow: interactive prompts (name, id auto-generated) → copy template →\n * rewrite manifest + package.json → install dependencies → validate the\n * manifest → print the dev loop entry points.\n *\n * create-hoardodile-plugin my-plugin\n * create-hoardodile-plugin my-plugin --tarballs ../hoardodile/tmp/sdks\n *\n * `--tarballs <dir>` rewires the SDK deps to packed tarballs plus a\n * pnpm-workspace.yaml with the cross-package overrides — used by the\n * release smoke test and for offline installs.\n */\nimport { spawn } from \"node:child_process\"\nimport {\n\tcpSync,\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\twriteFileSync,\n} from \"node:fs\"\nimport { dirname, join, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport {\n\tcancel,\n\tconfirm,\n\tintro,\n\tisCancel,\n\tlog,\n\toutro,\n\ttext,\n} from \"@clack/prompts\"\nimport { pluginManifest } from \"@hoardodile/sdk-types/schema\"\nimport {\n\trewriteManifest,\n\trewritePackageJson,\n\ttarballOverridesYaml,\n} from \"./rewrite.ts\"\n\nconst ROOT = resolve(dirname(fileURLToPath(import.meta.url)), \"..\")\n// The template ships inside dist (dist/template) — the bin always runs\n// from the built artifact, so resolve relative to this module.\nconst TEMPLATE_DIR = resolve(\n\tdirname(fileURLToPath(import.meta.url)),\n\t\"template\",\n)\nconst SELF_VERSION = JSON.parse(\n\treadFileSync(join(ROOT, \"package.json\"), \"utf8\"),\n).version\n\nfunction writeJson(path: string, value: unknown): void {\n\twriteFileSync(path, `${JSON.stringify(value, null, \"\\t\")}\\n`)\n}\n\nfunction writeTarballOverrides(targetDir: string, tarballsDir: string): void {\n\twriteFileSync(\n\t\tjoin(targetDir, \"pnpm-workspace.yaml\"),\n\t\ttarballOverridesYaml(tarballsDir, SELF_VERSION),\n\t)\n}\n\nasync function runInstall(targetDir: string): Promise<void> {\n\tlog.step(\"Installing dependencies (pnpm install)\")\n\treturn new Promise<void>((resolveDone, reject) => {\n\t\tconst child = spawn(\"pnpm\", [\"install\"], {\n\t\t\tcwd: targetDir,\n\t\t\tstdio: \"inherit\",\n\t\t\tshell: process.platform === \"win32\",\n\t\t})\n\t\tchild.on(\"error\", reject)\n\t\tchild.on(\"exit\", (code) =>\n\t\t\tcode === 0\n\t\t\t\t? resolveDone()\n\t\t\t\t: reject(new Error(`pnpm install exited ${code}`)),\n\t\t)\n\t})\n}\n\nexport async function main() {\n\tconst positional = process.argv.slice(2).filter((a) => !a.startsWith(\"--\"))\n\tconst tarballsFlag = process.argv.indexOf(\"--tarballs\")\n\tconst tarballsDir =\n\t\ttarballsFlag !== -1\n\t\t\t? resolve(process.argv[tarballsFlag + 1] ?? \"\")\n\t\t\t: undefined\n\n\tintro(\"hoardodile plugin scaffold\")\n\n\tconst providedName = positional[0]\n\tconst pluginName =\n\t\tprovidedName ??\n\t\t(await text({\n\t\t\tmessage: \"Plugin name (also the directory name, npm-style):\",\n\t\t\tvalidate: (v) =>\n\t\t\t\t/^[a-z0-9][a-z0-9-]*$/.test(v ?? \"\")\n\t\t\t\t\t? undefined\n\t\t\t\t\t: \"lowercase letters, digits and dashes only\",\n\t\t}))\n\tif (isCancel(pluginName)) {\n\t\tcancel(\"scaffold cancelled\")\n\t\tprocess.exit(1)\n\t}\n\n\tconst targetDir = resolve(pluginName)\n\tif (existsSync(targetDir) && readdirSync(targetDir).length > 0) {\n\t\tconst proceed = await confirm({\n\t\t\tmessage: `${targetDir} is not empty — scaffold into it anyway?`,\n\t\t\tinitialValue: false,\n\t\t})\n\t\tif (isCancel(proceed) || !proceed) {\n\t\t\tcancel(\"scaffold cancelled\")\n\t\t\tprocess.exit(1)\n\t\t}\n\t}\n\n\tlog.step(`Copying template into ${targetDir}`)\n\tmkdirSync(targetDir, { recursive: true })\n\t// dist/template is a build artifact (already filtered when copied in);\n\t// copy it wholesale so template source dirs inside it survive.\n\tcpSync(TEMPLATE_DIR, targetDir, { recursive: true })\n\n\tlog.step(\"Rewriting manifest and package.json\")\n\tconst manifestPath = join(targetDir, \"manifest.json\")\n\tconst pkgPath = join(targetDir, \"package.json\")\n\twriteJson(\n\t\tmanifestPath,\n\t\trewriteManifest(JSON.parse(readFileSync(manifestPath, \"utf8\")), pluginName),\n\t)\n\twriteJson(\n\t\tpkgPath,\n\t\trewritePackageJson(JSON.parse(readFileSync(pkgPath, \"utf8\")), pluginName, {\n\t\t\ttarballsDir,\n\t\t\tsdkVersion: SELF_VERSION,\n\t\t}),\n\t)\n\n\tconst parsed = pluginManifest.safeParse(\n\t\tJSON.parse(readFileSync(manifestPath, \"utf8\")),\n\t)\n\tif (!parsed.success) {\n\t\tprocess.exitCode = 1\n\t\tlog.error(`generated manifest is invalid: ${parsed.error.message}`)\n\t\treturn\n\t}\n\n\tif (tarballsDir !== undefined) {\n\t\tlog.step(`Rewiring SDK deps to tarballs in ${tarballsDir}`)\n\t\twriteTarballOverrides(targetDir, tarballsDir)\n\t}\n\n\ttry {\n\t\tawait runInstall(targetDir)\n\t} catch (err) {\n\t\tprocess.exitCode = 1\n\t\tlog.error(\n\t\t\t`install failed: ${err instanceof Error ? err.message : String(err)}`,\n\t\t)\n\t\treturn\n\t}\n\n\toutro(\"Plugin scaffolded. Next steps:\")\n\tconsole.log(` cd ${targetDir}`)\n\tconsole.log(\n\t\t\" pnpm build # build dist/ (client + server bundle + manifest)\",\n\t)\n\tconsole.log(\n\t\t\" pnpm dev # watch-build + serve the workbench (http://127.0.0.1:5199)\",\n\t)\n\tconsole.log(\" pnpm test # unit tests against the fixture API\")\n\tconsole.log(\" hoardodile plugin run detect testdata --plugin-dir dist\")\n\tconsole.log(\n\t\t\" # Replace the manifest id? No — it was generated fresh. Zip dist/ contents and upload in Settings → Plugins.\",\n\t)\n}\n\nif (\n\tprocess.argv[1] !== undefined &&\n\tresolve(process.argv[1]) === fileURLToPath(import.meta.url)\n) {\n\tmain().catch((err) => {\n\t\tlog.error(err instanceof Error ? err.message : String(err))\n\t\tprocess.exit(1)\n\t})\n}\n"]}
@@ -1,5 +1,13 @@
1
1
  name: release
2
2
 
3
+ # Normally the tag is produced by `pnpm release <version>` (release-it bumps
4
+ # package.json + manifest.json, writes CHANGELOG.md, commits, tags and pushes).
5
+ # This workflow is the publisher: on tag push it builds, packages
6
+ # (`release/<id>-<version>.zip` + `.sha256`) and creates a *published* GitHub
7
+ # release the marketplace picks up — so no local GITHUB_TOKEN or `gh` CLI is
8
+ # needed. Pushing the tag manually (`git tag v<version> && git push origin
9
+ # v<version>`) still works as a fallback.
10
+
3
11
  on:
4
12
  push:
5
13
  tags: ["v*"]
@@ -35,12 +43,15 @@ jobs:
35
43
  - name: Package the release artifacts
36
44
  run: pnpm exec hoardodile plugin package --skip-build
37
45
 
46
+ - name: Gate the introduction (intro/ must be flat, images by bare filename)
47
+ run: pnpm intro:check
48
+
38
49
  - name: Publish the GitHub release
39
50
  env:
40
51
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41
52
  run: |
42
53
  files=$(ls release/*)
43
- if ls intro.*.md >/dev/null 2>&1; then
44
- files="$files intro.*.md"
54
+ if [ -d intro ]; then
55
+ files="$files intro/*"
45
56
  fi
46
57
  gh release create "$GITHUB_REF_NAME" $files --generate-notes
@@ -29,16 +29,24 @@ detect hook through the same sandboxed worker the server uses.
29
29
 
30
30
  ## Releases
31
31
 
32
- Bump `version` in `manifest.json` on user-visible changes, then:
32
+ Bump `version` in `manifest.json` on user-visible changes, then release with
33
+ one command:
33
34
 
34
35
  ```bash
35
- git tag v<version> && git push origin v<version>
36
+ pnpm release <version>
36
37
  ```
37
38
 
38
- The tag must match `v<manifest.version>` `.github/workflows/release.yml`
39
- builds, runs `hoardodile plugin package` (`release/<id>-<version>.zip` +
40
- `.sha256`) and publishes the GitHub release. Your version is independent of
41
- the hoardodile app version.
39
+ release-it bumps `package.json` **and** `manifest.json` (kept in lockstep by
40
+ `scripts/sync-version.mjs`), writes `CHANGELOG.md` from Conventional Commits,
41
+ commits `chore(release): v<version>`, tags and pushes. Run it on `main` with a
42
+ clean working tree. `.github/workflows/release.yml` then builds, runs
43
+ `hoardodile plugin package` (`release/<id>-<version>.zip` + `.sha256`) and
44
+ publishes the GitHub release. The tag must match `v<manifest.version>` — the
45
+ workflow fails otherwise. Your version is independent of the hoardodile app
46
+ version.
47
+
48
+ Pushing the tag by hand still works as a fallback:
49
+ `git tag v<version> && git push origin v<version>`.
42
50
 
43
51
  ## Marketplace publishing
44
52
 
@@ -65,26 +65,34 @@ server uses — the exact production execution path.
65
65
 
66
66
  ## Deploying
67
67
 
68
- Publish to the marketplace with two steps:
68
+ Publish to the marketplace with one command:
69
69
 
70
70
  ```bash
71
71
  # 1. Add the repository address to your registry repo's registry.json:
72
72
  # { "version": 1, "plugins": ["https://github.com/<owner>/<repo>"] }
73
73
 
74
- # 2. Tag the release — `.github/workflows/release.yml` builds, packages
75
- # (`release/<id>-<version>.zip` + `.sha256`) and publishes the GitHub
76
- # release automatically. No local `gh` CLI or token needed.
77
- git tag v<version> && git push origin v<version>
74
+ # 2. One-click release — release-it bumps the version in package.json AND
75
+ # manifest.json, writes CHANGELOG.md from Conventional Commits, commits,
76
+ # tags `v<version>` and pushes. The tag-triggered `.github/workflows/
77
+ # release.yml` then builds, packages (`release/<id>-<version>.zip` +
78
+ # `.sha256`) and publishes the GitHub release. No local `gh` CLI or token.
79
+ pnpm release <version>
78
80
  ```
79
81
 
80
- The tag must match the manifest version (`v<manifest.version>`) — the
81
- workflow fails otherwise. Then paste the registry repo address once in
82
- **Settings → Marketplace**. The app reads the registry, each plugin's
83
- manifest and its latest release — names, versions, permissions and
84
- release notes come straight from GitHub, so the list never needs editing
85
- again. The zip asset is `<id>-<version>.zip` (produced by
86
- `hoardodile plugin package`); before the first release the plugin shows
87
- up with a "no release" state.
82
+ On `main`, with a clean working tree. The tag must match the manifest version
83
+ (`v<manifest.version>`) — the workflow fails otherwise. Then paste the registry
84
+ repo address once in **Settings → Marketplace**. The app reads the registry,
85
+ each plugin's manifest and its latest release — names, versions, permissions and
86
+ release notes come straight from GitHub, so the list never needs editing again.
87
+ The zip asset is `<id>-<version>.zip` (produced by `hoardodile plugin package`);
88
+ before the first release the plugin shows up with a "no release" state.
89
+
90
+ Prefer pushing a tag manually (no release-it)? It still works — the workflow
91
+ does the same build/package/publish:
92
+
93
+ ```bash
94
+ git tag v<version> && git push origin v<version>
95
+ ```
88
96
 
89
97
  Local installs (zip upload in **Settings → Plugins**) still work for
90
98
  private packages.
@@ -92,13 +100,34 @@ private packages.
92
100
  ## Publishing an introduction
93
101
 
94
102
  The marketplace detail view shows a per-release **Intro** tab. Ship one
95
- markdown file per supported language at the repository root, named
96
- `intro.<locale>.md` (e.g. `intro.en.md`, `intro.zh.md`) — `release.yml`
97
- uploads them alongside the zip, so **each release carries its own
98
- introduction** and every version shows independent notes. Use the app's
99
- supported language codes as file names (`en`, `zh`, `ja`, `de`, `es`) —
100
- a region-coded name like `intro.zh-CN.md` only matches a UI language
101
- resolved to that exact code, so `intro.zh.md` is what Chinese users see.
103
+ markdown file per supported language inside the **`intro/` folder**, named
104
+ `intro.<locale>.md` (e.g. `intro/intro.en.md`, `intro/intro.zh.md`) —
105
+ `release.yml` uploads the whole folder alongside the zip, so **each release
106
+ carries its own introduction** and every version shows independent notes.
107
+ Use the app's supported language codes as file names (`en`, `zh`, `ja`,
108
+ `de`, `es`) — a region-coded name like `intro.zh-CN.md` only matches a UI
109
+ language resolved to that exact code, so `intro.zh.md` is what Chinese
110
+ users see.
111
+
112
+ ### Adding images
113
+
114
+ An introduction may reference images. Place the image in `intro/` and
115
+ reference it by its **bare filename**:
116
+
117
+ ```md
118
+ ![Plugin screenshot](screenshot.png)
119
+ ```
120
+
121
+ Every file in `intro/` is published as a release asset on each release, and
122
+ the app resolves a relative image reference against that release's download
123
+ URL. Because a GitHub release is a flat list of assets, the `intro/` folder
124
+ must stay **flat** and references must be bare filenames — a nested path
125
+ like `![alt](img/shot.png)` resolves to a URL the release does not serve and
126
+ the image breaks. Absolute `http(s)://` and `data:` image URIs are allowed.
127
+
128
+ `pnpm intro:check` (run by `release.yml` before publishing) gates this: it
129
+ fails the release if `intro/` is absent-and-required, is not flat, ships no
130
+ `intro.<locale>.md`, or references an image by a nested/missing path.
102
131
 
103
132
  The app resolves the intro for the user's UI language (exact locale → base
104
133
  language → `en` → the only shipped language); the release body always shows
@@ -10,7 +10,33 @@
10
10
  "watch": "hoardodile plugin build --watch",
11
11
  "detect:smoke": "hoardodile plugin run detect testdata --plugin-dir dist",
12
12
  "lint": "tsc --noEmit",
13
- "test": "vitest run"
13
+ "test": "vitest run",
14
+ "intro:check": "node scripts/check-intro.mjs",
15
+ "release": "node scripts/release.mjs"
16
+ },
17
+ "release-it": {
18
+ "git": {
19
+ "commitMessage": "chore(release): v${version}",
20
+ "tagName": "v${version}",
21
+ "requireBranch": "main"
22
+ },
23
+ "github": {
24
+ "release": false
25
+ },
26
+ "npm": {
27
+ "publish": false
28
+ },
29
+ "plugins": {
30
+ "@release-it/conventional-changelog": {
31
+ "preset": {
32
+ "name": "conventionalcommits"
33
+ },
34
+ "infile": "CHANGELOG.md"
35
+ }
36
+ },
37
+ "hooks": {
38
+ "after:bump": "node scripts/sync-version.mjs"
39
+ }
14
40
  },
15
41
  "dependencies": {
16
42
  "@hoardodile/sdk-react": "workspace:*",
@@ -29,6 +55,8 @@
29
55
  "jsdom": "catalog:",
30
56
  "typescript": "catalog:",
31
57
  "vite": "catalog:",
32
- "vitest": "catalog:"
58
+ "vitest": "catalog:",
59
+ "release-it": "catalog:",
60
+ "@release-it/conventional-changelog": "catalog:"
33
61
  }
34
62
  }
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Release gate for the plugin marketplace introduction images.
4
+ *
5
+ * The marketplace reads each release's `intro.<locale>.md` asset and the
6
+ * app resolves any image referenced inside it against the release's
7
+ * download URL. Because a GitHub release is a flat list of assets, every
8
+ * referenced image must be:
9
+ *
10
+ * 1. Shipped inside the `intro/` folder (the only folder the release
11
+ * workflow uploads), and
12
+ * 2. Referenced by its bare filename (`![alt](shot.png)`), never a
13
+ * nested path (`img/shot.png`) — a nested path resolves to a URL the
14
+ * release does not actually serve, so the image is silently broken.
15
+ *
16
+ * This gate fails a build/release when the `intro/` folder is absent, is
17
+ * not flat, ships no `intro.<locale>.md`, or references an image by a
18
+ * nested/missing path. External `http(s)://` and `data:` image URIs are
19
+ * allowed (they are not release assets).
20
+ *
21
+ * Usage:
22
+ * node scripts/check-intro.mjs # checks ./intro
23
+ * node scripts/check-intro.mjs <dir> # checks <dir>/intro
24
+ *
25
+ * Dependency-free on purpose — it ships inside every scaffolded plugin.
26
+ */
27
+
28
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
29
+ import { join, resolve } from "node:path"
30
+
31
+ const ROOT = resolve(process.argv[2] ?? process.cwd())
32
+ const INTRO_DIR = join(ROOT, "intro")
33
+
34
+ const RULE_SUMMARY =
35
+ "intro/ must be flat; each intro.<locale>.md image is referenced by a " +
36
+ "bare filename that exists in intro/ (absolute http(s)/data URIs are ok)"
37
+
38
+ // `![alt](src)`, `![alt](src "title")`, `<img src="x">` (`src` images only).
39
+ const MARKDOWN_IMG_RE = /!\[[^\]]*\]\(([^)\s]+)(?:\s+["'][^"']*["'])?\)/g
40
+ const HTML_IMG_RE = /<img\b[^>]*\bsrc\s*=\s*(["'])([^"']+)\1/gi
41
+ const EXT_URL_RE = /^(?:https?:\/\/|data:)/i
42
+
43
+ function isRelative(src) {
44
+ return !EXT_URL_RE.test(src)
45
+ }
46
+
47
+ /**
48
+ * A relative reference is valid only as a flat bare filename that exists
49
+ * inside `intro/` — not a nested path and not a missing/unshipsed file.
50
+ */
51
+ function resolveRelativeRef(src, file, issues) {
52
+ const trimmed = src.trim().replace(/^\.\//, "")
53
+ if (trimmed.length === 0) {
54
+ issues.push(`${file}: empty image reference`)
55
+ return
56
+ }
57
+ if (
58
+ trimmed.includes("/") ||
59
+ trimmed.includes("\\") ||
60
+ trimmed.includes("..")
61
+ ) {
62
+ issues.push(
63
+ `${file}: image "${src}" is not flat — use a bare filename like "shot.png" (release assets are flat)`,
64
+ )
65
+ return
66
+ }
67
+ const target = join(INTRO_DIR, trimmed)
68
+ if (!existsSync(target) || !statSync(target).isFile()) {
69
+ issues.push(
70
+ `${file}: image "${src}" is not in intro/ — every referenced image must be committed there (it is published with the release)`,
71
+ )
72
+ }
73
+ }
74
+
75
+ function collectImageRefs(text) {
76
+ const refs = []
77
+ for (const match of text.matchAll(MARKDOWN_IMG_RE)) refs.push(match[1])
78
+ for (const match of text.matchAll(HTML_IMG_RE)) refs.push(match[2])
79
+ return refs
80
+ }
81
+
82
+ function main() {
83
+ if (!existsSync(INTRO_DIR)) {
84
+ console.log(
85
+ "[check-intro] no intro/ folder — nothing to gate (a release without an introduction is valid).",
86
+ )
87
+ return
88
+ }
89
+
90
+ const entries = readdirSync(INTRO_DIR, { withFileTypes: true })
91
+ const issues = []
92
+
93
+ // 1. Flat-only: any subdirectory makes the folder publish incorrectly.
94
+ for (const entry of entries) {
95
+ if (entry.isDirectory()) {
96
+ issues.push(
97
+ `intro/${entry.name}/ is a subdirectory — the intro folder must be flat (release assets are a flat list)`,
98
+ )
99
+ }
100
+ }
101
+
102
+ const mdFiles = entries
103
+ .filter(
104
+ (entry) =>
105
+ entry.isFile() && /^intro\.[A-Za-z0-9-]+\.md$/.test(entry.name),
106
+ )
107
+ .map((entry) => entry.name)
108
+
109
+ if (mdFiles.length === 0) {
110
+ issues.push(
111
+ "intro/ has no intro.<locale>.md — the release would ship images but the marketplace could not display an introduction",
112
+ )
113
+ }
114
+
115
+ // 2. Flat, present image references inside each intro markdown.
116
+ for (const name of mdFiles) {
117
+ const text = readFileSync(join(INTRO_DIR, name), "utf-8")
118
+ for (const src of collectImageRefs(text)) {
119
+ if (isRelative(src)) resolveRelativeRef(src, `intro/${name}`, issues)
120
+ }
121
+ }
122
+
123
+ if (issues.length > 0) {
124
+ console.error("[check-intro] gate failed:")
125
+ for (const issue of issues) console.error(` - ${issue}`)
126
+ console.error(`\n${RULE_SUMMARY}`)
127
+ process.exit(1)
128
+ }
129
+
130
+ console.log(
131
+ `[check-intro] intro/ ok — ${mdFiles.length} introduction file(s), flat references only.`,
132
+ )
133
+ }
134
+
135
+ main()
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `pnpm release <version>` — one-click release for this plugin.
4
+ *
5
+ * Runs release-it, which bumps `package.json`, syncs the new version into
6
+ * `manifest.json` (after:bump → scripts/sync-version.mjs), writes a
7
+ * Conventional-Commits `CHANGELOG.md`, commits `chore(release): v<version>`,
8
+ * tags `v<version>` and pushes. Publishing the GitHub release itself is CI's
9
+ * job: the pushed tag triggers `.github/workflows/release.yml`, which builds,
10
+ * packages (`release/<id>-<version>.zip` + `.sha256`) and creates the
11
+ * published release the marketplace picks up — so no local GITHUB_TOKEN or
12
+ * `gh` CLI is needed. Manual `git tag v<version> && git push origin v<version>`
13
+ * still works as a fallback.
14
+ *
15
+ * release-it is launched with process.execPath — on Windows the `pnpm`/`.cmd`
16
+ * shims are not reliably launchable, so invoking the JS entry is uniform.
17
+ *
18
+ * node scripts/release.mjs [release-it args...]
19
+ */
20
+
21
+ import { spawnSync } from "node:child_process"
22
+ import { existsSync } from "node:fs"
23
+ import { dirname, resolve } from "node:path"
24
+ import { fileURLToPath } from "node:url"
25
+
26
+ const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..")
27
+
28
+ const releaseItBin = resolve(
29
+ ROOT,
30
+ "node_modules",
31
+ "release-it",
32
+ "bin",
33
+ "release-it.js",
34
+ )
35
+ if (!existsSync(releaseItBin)) {
36
+ console.error(
37
+ `release-it not found at ${releaseItBin} — run pnpm install first.`,
38
+ )
39
+ process.exit(1)
40
+ }
41
+
42
+ const result = spawnSync(
43
+ process.execPath,
44
+ [releaseItBin, ...process.argv.slice(2)],
45
+ {
46
+ stdio: "inherit",
47
+ env: process.env,
48
+ },
49
+ )
50
+ if (result.error) {
51
+ console.error(`failed to launch release-it: ${result.error}`)
52
+ process.exit(1)
53
+ }
54
+ process.exit(result.status ?? 1)
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sync the plugin version from `package.json` into `manifest.json`. Invoked by
4
+ * release-it's after:bump hook (the version was just bumped in package.json),
5
+ * so the tag `v<version>` and the packaged zip name match the manifest the
6
+ * marketplace reads. Idempotent; also safe to run standalone.
7
+ *
8
+ * `manifest.json` is biome-canonical, so patch only the version value —
9
+ * JSON.parse + JSON.stringify would reflow arrays and need a post-format pass.
10
+ *
11
+ * node scripts/sync-version.mjs
12
+ */
13
+
14
+ import { readFileSync, writeFileSync } from "node:fs"
15
+
16
+ const { version } = JSON.parse(readFileSync("package.json", "utf8"))
17
+ const path = "manifest.json"
18
+
19
+ let source
20
+ try {
21
+ source = readFileSync(path, "utf8")
22
+ } catch {
23
+ throw new Error(`could not read ${path} — is this the plugin root?`)
24
+ }
25
+
26
+ const next = source.replace(/("version"\s*:\s*)"[^"]*"/, `$1"${version}"`)
27
+ if (next === source) {
28
+ console.log(`unchanged ${path} (${version})`)
29
+ process.exit(0)
30
+ }
31
+
32
+ // Guard: the patch must stay valid JSON carrying the requested version.
33
+ const patched = JSON.parse(next)
34
+ if (patched.version !== version) {
35
+ throw new Error(`version field mismatch after syncing ${path}`)
36
+ }
37
+ writeFileSync(path, next)
38
+ console.log(`synced ${path} -> ${version}`)
@@ -21,6 +21,21 @@ export default definePlugin<TemplateSchema>({
21
21
  // so sourceMeta never rescans.
22
22
  { ok: true, hdtplCount: hdtpl.length }
23
23
  },
24
+ // Optional: the host runs this once after a successful install/update
25
+ // commit (marketplace install/update, zip uploads), with an
26
+ // install-scoped API — no resource is attached (the file surface is
27
+ // empty) but `download` still works behind the shared consent
28
+ // dialog. Best-effort by contract: a throw (or a denied consent)
29
+ // never fails the install, so re-check at runtime.
30
+ //
31
+ // onInstall: async (api) => {
32
+ // await api.download({
33
+ // url: "https://example.com/runtime.min.js",
34
+ // dest: "runtime/runtime.min.js",
35
+ // sha256: "…64 lowercase hex…",
36
+ // reason: "…shown in the consent dialog…",
37
+ // })
38
+ // },
24
39
  sourceMeta: buildSourceMeta,
25
40
  })
26
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoardodile/create-plugin",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
5
  "description": "Scaffold a hoardodile content plugin.",
6
6
  "keywords": [
@@ -37,7 +37,7 @@
37
37
  "dependencies": {
38
38
  "@clack/prompts": "^1.7.0",
39
39
  "zod": "^4.4.3",
40
- "@hoardodile/sdk-types": "0.1.2"
40
+ "@hoardodile/sdk-types": "0.1.4"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^26.2.0",